Jetson AGX Orin 기반 Docker 컨테이너에서의 ROS2 활용 안내서 (2025-09-25)
1. 서론
1.1 PX4 SITL 시뮬레이션의 중요성
자율 시스템 개발에서 Software-in-the-Loop (SITL) 시뮬레이션은 필수적인 과정이다. SITL은 실제 하드웨어 없이 비행 제어 펌웨어 스택 전체를 컴퓨터 상에서 실행하여 가상의 기체와 환경에서 테스트하는 기술을 의미한다. 이 접근법은 물리적 손상 위험 없이 새로운 알고리즘, 비행 모드, 페일세이프 로직 등을 검증할 수 있게 하여 개발의 안전성을 극대화한다. 또한, 고가의 드론 하드웨어나 테스트 장비 없이도 개발을 진행할 수 있어 비용 효율적이며, 실제 비행 테스트 준비에 소요되는 시간을 절약하여 개발 주기를 획기적으로 단축시킨다.1 본 안내서는 세계적으로 가장 널리 사용되는 오픈소스 자동 비행 소프트웨어인 PX4-Autopilot과 강력한 3D 로봇 시뮬레이터인 Gazebo를 Docker 컨테이너 환경에서 연동하는 방법에 대해 심도 있게 다룬다.
1.2 Gazebo 시뮬레이션 환경 요구사항
최신 PX4-Autopilot 개발 및 테스트를 위해서는 현대적인 Gazebo 시뮬레이터가 필수적이다. PX4는 최신 기능과 안정적인 성능을 위해 Gazebo Harmonic 버전 사용을 공식적으로 요구하며 권장한다.2 이 시뮬레이터는 향상된 렌더링 파이프라인, 정교한 물리 엔진, 개선된 센서 모델을 특징으로 하여 보다 현실적인 테스트 환경을 제공한다.
PX4 빌드 시스템에서 Gazebo Harmonic을 사용하는 시뮬레이션 타겟은 gz_ 접두사로 명확히 구분된다 (예: make px4_sitl gz_x500). 본 안내서는 PX4의 공식 요구사항에 맞춰 Gazebo Harmonic을 사용하는 것을 기준으로 모든 내용을 서술한다. 이를 통해 사용자는 최신 개발 환경에서 발생할 수 있는 호환성 문제를 최소화하고 시뮬레이션의 모든 잠재력을 활용할 수 있을 것이다.
2. Docker 기반 개발 환경 구축
Docker를 사용하면 복잡한 개발 툴체인 설치 과정을 단순화하고, 어떤 시스템에서든 일관된 개발 및 시뮬레이션 환경을 보장할 수 있다. PX4는 Gazebo 시뮬레이터와 ROS가 사전 설치된 공식 Docker 이미지를 제공하여 사용자가 빠르게 개발에 착수할 수 있도록 지원한다.3
2.1 Docker 환경의 이점
- 일관성 및 재현성: 모든 개발자가 동일한 라이브러리와 도구 버전을 사용하여 “내 컴퓨터에서는 됐는데“와 같은 문제를 원천적으로 방지한다.
- 의존성 격리: 호스트 운영체제에 영향을 주지 않고 필요한 모든 개발 도구를 컨테이너 내에 격리하여 설치하므로, 시스템을 깨끗하게 유지할 수 있다.
- 간편한 설정: 복잡한 설치 스크립트를 실행할 필요 없이, 단일
docker run명령어로 전체 개발 환경을 즉시 실행할 수 있다.
2.2 개발 환경 준비
시뮬레이션을 시작하기 전에, 호스트 머신에 PX4 소스 코드를 클론한다. 이 디렉토리는 나중에 컨테이너와 공유되어 코드 수정 및 빌드 작업을 수행하게 된다.3
git clone https://github.com/PX4/PX4-Autopilot.git --recursive
2.3 PX4 개발 Docker 컨테이너 실행
PX4 개발팀은 ROS 및 시뮬레이션 도구가 포함된 다양한 버전의 이미지를 Docker Hub에 제공한다.4
px4io/px4-dev-simulation-bionic 이미지는 Gazebo 시뮬레이션을 위한 안정적인 환경을 제공한다.9
- X11 포워딩 설정: 컨테이너 내부의 Gazebo GUI를 호스트 머신에 표시하기 위해, 호스트 터미널에서 다음 명령어를 실행하여 로컬 도커 연결을 허용한다.1
xhost +local:docker
- 컨테이너 실행: 호스트의
PX4-Autopilot디렉토리에서 다음docker run명령어를 실행한다. 이 명령어는 컨테이너를 시작하고 필요한 모든 설정(소스 코드 공유, GUI 포워딩, 네트워크 연결)을 자동으로 구성한다.
docker run -it --privileged \
-v $(pwd):/home/user/PX4-Autopilot:rw \
-v /tmp/.X11-unix:/tmp/.X11-unix:ro \
-e DISPLAY=$DISPLAY \
--net=host \
--name=px4-gz-sim \
px4io/px4-dev-simulation-bionic bash
-it: 컨테이너와 상호작용할 수 있는 터미널을 연다.--privileged: GPU 등 호스트의 하드웨어 장치에 접근 권한을 부여하여 Gazebo 렌더링 성능을 향상시킨다.3-v $(pwd):/home/user/PX4-Autopilot:rw: 현재 호스트 디렉토리(PX4-Autopilot)를 컨테이너 내부의/home/user/PX4-Autopilot경로에 읽기/쓰기 모드로 연결(마운트)한다.13-v /tmp/.X11-unix...와-e DISPLAY=$DISPLAY: Gazebo GUI를 호스트 화면으로 전달(X11 포워딩)하기 위한 설정이다.15--net=host: 컨테이너가 호스트의 네트워크를 직접 사용하도록 설정한다. 이를 통해 호스트에서 실행 중인 QGroundControl이 별도의 설정 없이 컨테이너 내의 시뮬레이션에 자동으로 연결될 수 있다.--name=px4-gz-sim: 컨테이너에px4-gz-sim이라는 이름을 부여한다.px4io/px4-dev-simulation-bionic: 사용할 Docker 이미지 이름이다.bash: 컨테이너가 시작될 때 실행할 명령어(배시 셸 시작)이다.
참고: PX4 소스 코드에 포함된 헬퍼 스크립트 Tools/docker_run.sh를 사용하면 위 과정을 더 간편하게 수행할 수 있다.3
2.4 QGroundControl 연동
QGroundControl (QGC)은 시뮬레이션된 기체의 상태를 시각적으로 확인하고, 임무를 계획하며, 파라미터를 조정하는 데 필수적인 지상 관제 소프트웨어(GCS)이다.16
Docker 컨테이너를 --net=host 옵션으로 실행했다면, 호스트에서 QGC를 실행했을 때 컨테이너 내부에서 실행 중인 PX4 시뮬레이션에 자동으로 연결된다. 만약 연결되지 않는다면 호스트의 방화벽이 UDP 포트 14550을 차단하는지 확인해야 한다.17
3. 기본 Gazebo 시뮬레이션 실행
모든 시뮬레이션 실행 명령어는 Docker 컨테이너 내부의 PX4-Autopilot 소스 코드 디렉토리(/home/user/PX4-Autopilot)에서 실행해야 한다.
3.1 단일 기체 시뮬레이션 실행
가장 대표적인 쿼드콥터 모델인 x500을 시뮬레이션하려면 컨테이너 셸에서 다음 명령어를 입력한다.
make px4_sitl gz_x500
이 명령어는 내부적으로 다음의 과정을 수행한다:
- SITL(posix) 타겟으로 PX4 펌웨어를 빌드한다.
- 지정된 기체 모델(
x500)에 대한 Gazebo 시뮬레이션 환경을 로드한다. - PX4 SITL 프로세스를 시작한다.
- Gazebo 클라이언트(GUI)를 실행하고 PX4 SITL과 연결한다.10
3.2 지원 기체 모델 및 빌드 명령어
PX4는 다양한 종류의 기체를 지원하며, 각 기체에 맞는 시뮬레이션 모델과 파라미터가 사전 정의되어 있다. make 명령어 뒤에 원하는 기체 모델명을 지정하여 해당 시뮬레이션을 실행할 수 있다.
| 기체 종류 | 기체 모델명 | Make 명령어 | Airframe Autostart ID |
|---|---|---|---|
| 쿼드콥터 (기본) | x500 | make px4_sitl gz_x500 | 4001 |
| 쿼드콥터 (뎁스 카메라) | x500_depth | make px4_sitl gz_x500_depth | 4002 |
| 쿼드콥터 (비전) | x500_vision | make px4_sitl gz_x500_vision | 4005 |
| 표준 VTOL | standard_vtol | make px4_sitl gz_standard_vtol | 4003 |
| 고정익 비행기 | rc_cessna | make px4_sitl gz_rc_cessna | 4004 |
이 표는 공식 문서 3을 기반으로 작성되었다.
3.3 실행 확인 및 QGroundControl 연동
시뮬레이션이 성공적으로 실행되면 호스트 머신에 두 개의 주요 창이 나타난다.
- 컨테이너 터미널: PX4 부팅 로그가 출력된 후,
pxh>라는 프롬프트가 나타나며 명령 대기 상태가 된다. 이는 PX4 셸이 활성화되었음을 의미한다.19 - Gazebo 창: 3D 가상 환경에 선택한 기체 모델이 나타난다. 이 창은 컨테이너에서 X11 포워딩을 통해 호스트에 표시되는 것이다.
이 상태에서 호스트에 설치해 둔 QGroundControl을 실행하면, 잠시 후 화면 중앙에 기체 정보가 표시되며 자동으로 연결이 수립된다. QGC의 ‘Fly’ 뷰에서는 기체의 3D 모델, 지도 상의 위치, 인공 지평선, 고도, 속도 등 다양한 텔레메트리 정보를 실시간으로 확인할 수 있다.
4. 시뮬레이션 제어 및 상호작용
시뮬레이션이 실행되면 컨테이너 내부의 PX4 셸 또는 호스트의 QGroundControl을 통해 가상의 기체를 제어할 수 있다.
4.1 PX4 셸을 이용한 기본 제어
PX4 셸은 저수준의 시스템 접근과 빠른 명령 실행을 가능하게 하는 강력한 인터페이스이다.11
commander 모듈은 비행과 관련된 핵심 명령을 담당하며, 이를 통해 기본적인 비행 제어가 가능하다.
| 명령어 | 기능 | 사용 예시 |
|---|---|---|
commander arm | 기체를 시동(Arming)한다. 프로펠러가 회전할 준비 상태가 된다. | pxh> commander arm |
commander takeoff | 지정된 고도까지 자동으로 이륙하여 호버링한다. | pxh> commander takeoff |
commander land | 현재 위치에 자동으로 착륙한다. | pxh> commander land |
commander disarm | 기체의 시동을 끈다(Disarming). 프로펠러 회전을 멈춘다. | pxh> commander disarm |
commander mode <mode> | 비행 모드를 변경한다. (예: position, mission) | pxh> commander mode mission |
이 표는 5의 정보를 종합하여 작성되었다.
시뮬레이션이 시작되고 [ecl/EKF] commencing GPS fusion 메시지가 출력되면 기체가 비행 준비 상태가 된 것이다.5 이때 commander arm과 commander takeoff 명령을 순서대로 입력하면 Gazebo 창에서 기체가 이륙하는 것을 확인할 수 있다.
4.2 QGroundControl을 이용한 임무 비행
호스트에서 실행되는 QGroundControl은 직관적인 그래픽 인터페이스를 통해 복잡한 자율 비행 임무를 손쉽게 계획하고 실행할 수 있도록 지원한다.
- 임무 계획 (Plan View):
- QGC 좌측 상단의 ‘Plan’ 탭을 클릭하여 임무 계획 화면으로 전환한다.9
- 화면 좌측 상단의 ‘Add Waypoint’ 아이콘을 클릭한 후, 지도 상의 원하는 지점을 클릭하여 웨이포인트를 추가한다.
- 첫 번째 웨이포인트는 일반적으로 ’Takeoff’로 자동 설정된다. 각 웨이포인트를 클릭하면 우측 패널에서 고도, 체공 시간, 수용 반경 등 세부 파라미터를 조정할 수 있다.
- 임무의 마지막에는 ‘Land’ 또는 ‘Return to Launch’ 명령을 추가하여 비행을 안전하게 종료하도록 계획한다.
- 임무 업로드 및 실행 (Fly View):
- 임무 계획이 완료되면, 우측의 ‘File’ 툴을 사용하여 ‘Upload’ 버튼을 클릭, 계획된 임무를 시뮬레이션 기체에 전송한다.9
- QGC 좌측 상단의 ‘Fly’ 탭을 클릭하여 비행 화면으로 전환한다.
- 화면 좌측 하단의 ‘Mission’ 슬라이더를 오른쪽으로 밀어 임무 비행을 시작한다.12
- 임무가 시작되면 기체는 자동으로 이륙하여 계획된 웨이포인트들을 순서대로 따라 비행한다. QGC의 지도와 3D 뷰를 통해 기체의 비행 경로를 실시간으로 추적할 수 있다.
5. 고급 시뮬레이션 설정 및 사용자화
PX4-Gazebo 시뮬레이션은 환경 변수를 통해 매우 유연하게 설정할 수 있다. 이는 컨테이너 내부에서 make 명령어를 실행할 때 적용된다.
5.1 환경 변수를 통한 시뮬레이션 제어
make 명령어 앞에 환경 변수를 지정함으로써 시뮬레이션의 시작 조건과 동작 방식을 동적으로 변경할 수 있다.10
| 환경 변수 | 설명 | 사용 예시 |
|---|---|---|
PX4_SYS_AUTOSTART | 실행할 기체의 Airframe ID를 지정한다. make 명령어로 암시적으로 설정되지만, 직접 지정할 수도 있다. | PX4_SYS_AUTOSTART=4001 make... |
PX4_SIM_MODEL | 시뮬레이터에 새로 생성(spawn)할 기체 모델의 이름을 지정한다. | PX4_SIM_MODEL=gz_x500 make... |
PX4_GZ_MODEL_NAME | 시뮬레이터에 이미 존재하는 기체 모델에 연결할 때 해당 모델의 이름을 지정한다. PX4_SIM_MODEL과 상호 배타적이다. | PX4_GZ_MODEL_NAME=x500 make... |
PX4_GZ_MODEL_POSE | PX4_SIM_MODEL로 기체를 생성할 때의 초기 위치와 자세를 지정한다. 형식: "x,y,z,roll,pitch,yaw" (단위: m, rad). | PX4_GZ_MODEL_POSE="0,2,1,0,0,1.57" make... |
PX4_GZ_WORLD | 시뮬레이션을 시작할 때 로드할 Gazebo 월드 파일의 이름을 지정한다. | PX4_GZ_WORLD=warehouse make... |
HEADLESS | 1로 설정하면 Gazebo GUI 없이 백그라운드에서 시뮬레이션을 실행한다. | HEADLESS=1 make px4_sitl gz_x500 |
PX4_SIM_SPEED_FACTOR | 시뮬레이션 속도를 조절한다. 1.0이 실시간이며, 2.0은 2배속, 0.5는 0.5배속을 의미한다. | PX4_SIM_SPEED_FACTOR=2.0 make... |
PX4_GZ_SIM_RENDER_ENGINE | Gazebo의 렌더링 엔진을 지정한다. 가상 머신에서 렌더링 문제가 발생할 경우 ogre로 설정하여 해결할 수 있다. | PX4_GZ_SIM_RENDER_ENGINE=ogre make... |
이 표는 공식 문서 10의 정보를 기반으로 작성되었다.
5.2 특정 Gazebo 월드(World) 로딩
컨테이너 내부에서 창고 환경을 로드하여 장애물 회피 알고리즘을 테스트하려면 다음 명령어를 사용한다.
PX4_GZ_WORLD=warehouse make px4_sitl gz_x500
사용 가능한 월드 파일 목록은 컨테이너 내의 PX4-Autopilot/Tools/simulation/gz/worlds/ 디렉토리에서 확인할 수 있다.
5.3 헤드리스(Headless) 모드 실행
GUI는 상당한 시스템 리소스를 소모한다. 자동화된 테스트나 여러 시뮬레이션을 동시에 실행할 때 HEADLESS=1 플래그를 사용하여 GUI 없이 시뮬레이션을 실행하는 것이 효율적이다.3
HEADLESS=1 make px4_sitl gz_x500
이 모드에서도 호스트의 QGroundControl을 통해 기체 상태를 모니터링하고 제어할 수 있다.
5.4 PX4와 Gazebo 분리 실행
펌웨어 코드를 수정하고 테스트하는 과정에서 매번 Gazebo까지 재시작하는 것은 비효율적이다. Docker 환경에서도 Gazebo는 그대로 실행시켜 둔 채, 변경된 PX4 SITL 인스턴스만 재시작할 수 있다.
- 첫 번째 컨테이너 터미널에서 Gazebo 실행:
make타겟에_none_ide접미사를 붙여 Gazebo 서버와 클라이언트를 먼저 실행한다.
make px4_sitl_default gazebo_none_ide
- 두 번째 컨테이너 터미널에서 PX4 실행: 호스트에서 새 터미널을 열고,
docker exec명령어로 실행 중인 컨테이너에 접속하여 두 번째 셸을 연다.
docker exec -it px4-gz-sim bash
새로운 셸에서 PX4 SITL 실행 파일을 직접 실행한다.
./build/px4_sitl_default/bin/px4
이 방식을 사용하면 호스트에서 코드를 수정한 후, 두 번째 컨테이너 터미널에서 PX4만 재컴파일 및 재시작하여 테스트 시간을 크게 단축할 수 있다.19
6. 다중 기체 시뮬레이션
군집 비행, 다중 로봇 협력 등의 알고리즘을 개발하기 위해서는 여러 대의 기체를 동시에 시뮬레이션하는 환경이 필수적이다.
6.1 스크립트를 이용한 다중 기체 실행
PX4는 다중 기체 시뮬레이션을 간편하게 실행할 수 있는 셸 스크립트를 제공한다. 컨테이너 내부에서 Tools/gazebo_sitl_multiple_run.sh 스크립트를 실행하면 지정된 수의 기체를 자동으로 생성하고, 각 기체에 대해 독립적인 PX4 인스턴스와 통신 포트를 할당한다.8
예를 들어, 3대의 iris 쿼드콥터를 시뮬레이션하려면 컨테이너 셸에서 다음 명령어를 사용한다.
./Tools/gazebo_sitl_multiple_run.sh -n 3 -m iris
호스트의 QGroundControl을 실행하면 3대의 기체가 각각 ‘Vehicle 1’, ‘Vehicle 2’, ’Vehicle 3’으로 인식되며, 상단의 탭을 통해 각 기체를 개별적으로 선택하고 제어할 수 있다.
6.2 ROS 2 Launch 파일을 이용한 다중 기체 실행
ROS 2 환경에서는 launch 파일을 사용하여 다중 기체 시뮬레이션을 보다 체계적으로 관리할 수 있다. ros2 launch 시스템은 각 기체에 대한 PX4 SITL 인스턴스, Gazebo 모델 스폰, Micro XRCE-DDS Agent와의 연결을 하나의 파일에서 정의하고 실행할 수 있게 해준다. 이는 복잡한 다중 로봇 시스템을 구축하고 실험하는 데 매우 강력하고 확장 가능한 방법을 제공한다.
7. ROS 2 연동 및 Offboard 제어
Offboard 모드는 PX4의 자율 비행 능력을 외부 컴퓨팅 장치로 확장하는 핵심 기능이다. Docker 컨테이너 내에서 ROS 2 노드를 실행하여 실시간으로 비행 경로를 생성하고, 복잡한 알고리즘 기반의 제어를 수행할 수 있다.22
7.1 ROS 2 연동 개요
- Micro XRCE-DDS (ROS 2): PX4의 내부 통신 미들웨어인 uORB 메시지를 ROS 2의 통신 시스템인 DDS와 직접 연결하는 고성능 브릿지이다. MAVLink를 거치지 않고 직접 통신하므로 더 낮은 지연 시간과 높은 처리량을 제공한다.24
7.2 Micro XRCE-DDS (ROS 2) 설정 및 Offboard 제어 예제
Docker 환경에서의 ROS 2 연동은 여러 터미널을 통해 각기 다른 프로세스를 실행하는 방식으로 이루어진다. 모든 프로세스는 동일한 컨테이너 내에서 실행된다.
- PX4 SITL 실행 (첫 번째 컨테이너 터미널): RTPS 타겟으로 PX4를 빌드하여 Micro XRCE-DDS Client를 활성화한다.
make px4_sitl_rtps gz_x500
- Agent 실행 (두 번째 컨테이너 터미널): 호스트에서 새 터미널을 열고
docker exec로 컨테이너에 접속한 후, Micro XRCE-DDS Agent를 실행한다. PX4 개발 이미지에는 Agent가 사전 설치되어 있다.13
# 호스트에서 실행
docker exec -it px4-gz-sim bash
# 컨테이너 내부에서 실행
MicroXRCEAgent udp4 -p 8888
- ROS 2 노드 실행 (세 번째 컨테이너 터미널): 또 다른 터미널을 열어 컨테이너에 접속한 후, Offboard 제어를 위한 ROS 2 노드를 실행한다.
Offboard 제어의 핵심 조건: PX4는 안전을 위해 Offboard 모드로 전환하기 전, 외부 제어기가 정상적으로 작동하고 있음을 확인하는 절차를 요구한다. 따라서, 모드 변경을 요청하기 전에 반드시 2Hz 이상의 빈도로 OffboardControlMode 메시지를 지속적으로 발행해야 한다. 이 조건을 만족하지 않으면 PX4는 Offboard 모드 전환 요청을 거부한다.
Python 예제 코드: 다음은 ROS 2를 사용하여 기체를 이륙시키고 특정 지점으로 이동시키는 간단한 Python 예제이다. 이 코드는 컨테이너 내부의 ROS 2 작업 공간에서 실행되어야 한다.
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from rclpy.qos import QoSProfile, ReliabilityPolicy, HistoryPolicy, DurabilityPolicy
from px4_msgs.msg import OffboardControlMode, TrajectorySetpoint, VehicleCommand, VehicleStatus
from rclpy.clock import Clock
class OffboardControl(Node):
def __init__(self):
super().__init__('offboard_control_takeoff_and_land')
qos_profile = QoSProfile(
reliability=ReliabilityPolicy.BEST_EFFORT,
durability=DurabilityPolicy.TRANSIENT_LOCAL,
history=HistoryPolicy.KEEP_LAST,
depth=1
)
self.status_sub = self.create_subscription(
VehicleStatus, '/fmu/out/vehicle_status', self.vehicle_status_callback, qos_profile)
self.offboard_control_mode_publisher = self.create_publisher(
OffboardControlMode, '/fmu/in/offboard_control_mode', qos_profile)
self.trajectory_setpoint_publisher = self.create_publisher(
TrajectorySetpoint, '/fmu/in/trajectory_setpoint', qos_profile)
self.vehicle_command_publisher = self.create_publisher(
VehicleCommand, '/fmu/in/vehicle_command', qos_profile)
self.timer = self.create_timer(0.1, self.timer_callback) # 10Hz
self.offboard_setpoint_counter = 0
self.vehicle_status = VehicleStatus()
def vehicle_status_callback(self, msg):
self.vehicle_status = msg
def arm(self):
self.publish_vehicle_command(VehicleCommand.VEHICLE_CMD_COMPONENT_ARM_DISARM, param1=1.0)
self.get_logger().info("Arm command sent")
def disarm(self):
self.publish_vehicle_command(VehicleCommand.VEHICLE_CMD_COMPONENT_ARM_DISARM, param1=0.0)
self.get_logger().info("Disarm command sent")
def publish_vehicle_command(self, command, **params):
msg = VehicleCommand()
msg.command = command
msg.param1 = params.get("param1", 0.0)
msg.param2 = params.get("param2", 0.0)
msg.param3 = params.get("param3", 0.0)
msg.param4 = params.get("param4", 0.0)
msg.param5 = params.get("param5", 0.0)
msg.param6 = params.get("param6", 0.0)
msg.param7 = params.get("param7", 0.0)
msg.target_system = 1
msg.target_component = 1
msg.source_system = 1
msg.source_component = 1
msg.from_external = True
msg.timestamp = int(Clock().now().nanoseconds / 1000)
self.vehicle_command_publisher.publish(msg)
def publish_offboard_control_mode(self):
msg = OffboardControlMode()
msg.position = True
msg.velocity = False
msg.acceleration = False
msg.attitude = False
msg.body_rate = False
msg.timestamp = int(Clock().now().nanoseconds / 1000)
self.offboard_control_mode_publisher.publish(msg)
def publish_trajectory_setpoint(self):
msg = TrajectorySetpoint()
msg.position = [0.0, 0.0, -5.0]
msg.yaw = -3.14
msg.timestamp = int(Clock().now().nanoseconds / 1000)
self.trajectory_setpoint_publisher.publish(msg)
def timer_callback(self):
self.publish_offboard_control_mode()
if self.offboard_setpoint_counter == 10:
self.publish_vehicle_command(VehicleCommand.VEHICLE_CMD_DO_SET_MODE, param1=1.0, param2=6.0)
self.arm()
if self.vehicle_status.nav_state == VehicleStatus.NAVIGATION_STATE_OFFBOARD:
self.publish_trajectory_setpoint()
if self.offboard_setpoint_counter < 11:
self.offboard_setpoint_counter += 1
def main(args=None):
rclpy.init(args=args)
offboard_control = OffboardControl()
rclpy.spin(offboard_control)
offboard_control.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
8. 문제 해결
Docker 환경은 많은 문제를 해결해주지만, 고유한 문제들이 발생할 수 있다.
8.1 시뮬레이션 실행 오
-
문제: Gazebo 창이 검게 나오거나 그래픽이 깨지는 등 렌더링 문제가 발생한다.
-
원인 및 해결: 호스트 시스템의 그래픽 드라이버와 컨테이너 환경 간의 호환성 문제일 수 있다. 먼저,
docker run명령어에--privileged옵션이 포함되었는지 확인한다. 그래도 문제가 지속되면,PX4_GZ_SIM_RENDER_ENGINE환경 변수를ogre로 설정하여 구버전 렌더링 엔진을 사용하도록 강제하면 해결될 수 있다.10
PX4_GZ_SIM_RENDER_ENGINE=ogre make px4_sitl gz_x500
-
문제: 시뮬레이션 시작 시
ERROR [gz_bridge] Task start failed (-1)또는gz_bridge failed to start and spawn model오류와 함께 PX4가 종료된다. -
원인 및 해결: 이전에 비정상적으로 종료된 Gazebo 서버(
gz server) 프로세스가 컨테이너 내부에 여전히 실행 중일 때 주로 발생한다.pkill명령어를 사용하여 컨테이너 내의 모든 Gazebo 관련 프로세스를 강제 종료한 후 다시 시도하면 해결된다.
pkill -f gazebo
pkill -f gz
8.2 QGroundControl 연결 문제
- 문제: 시뮬레이션은 정상적으로 실행되지만 호스트의 QGroundControl이 기체를 자동으로 인식하지 못한다.
- 원인 및 해결:
- 네트워크 설정 확인:
docker run명령어에--net=host옵션이 사용되었는지 확인한다. 이 옵션이 없으면 자동 연결이 작동하지 않는다. - 방화벽 확인: 호스트 운영체제의 방화벽이 QGC와 PX4 SITL 간의 UDP 통신(특히 포트 14550)을 차단하는지 확인한다. 방화벽 설정에서 QGroundControl 애플리케이션에 대한 인바운드 규칙을 허용해야 할 수 있다.
- 수동 연결: 자동 연결이 계속 실패하면 QGC의
Application Settings > Comm Links에서 새로운 UDP 링크를 수동으로 추가한다. 서버 주소는127.0.0.1, 포트는14550으로 설정하여 연결을 시도한다.
8.3 시뮬레이션 중 기체 동작 이상
-
문제:
commander takeoff명령 후 기체가 제어 불능 상태로 무한히 상승한다. -
원인 및 해결: 이는 시뮬레이션 모델의 물리 파라미터가 실제 기체의 특성과 맞지 않을 때 발생하며, Docker 사용 여부와는 무관하다. 기체의 SDF(
.sdf) 파일 내에 정의된 모터 상수(motorConstant), 최대 회전 속도(maxRotVelocity) 등의 값을 조정해야 한다. 목표는 호버링 시 추력이 약 50% 수준이 되도록 파라미터를 조정하는 것이다.26 -
문제: 기체의 고도 또는 위치가 Gazebo 환경의 실제 지면과 일치하지 않는다.
-
원인 및 해결: 홈 포지션의 고도 값 문제일 수 있다.
PX4_HOME_LAT,PX4_HOME_LON,PX4_HOME_ALT환경 변수를 사용하여 컨테이너 내부에서 시뮬레이션을 시작할 때 이륙 위치의 위도, 경도, 고도를 명시적으로 설정하면 이 문제를 해결할 수 있다.19
export PX4_HOME_LAT=47.397742
export PX4_HOME_LON=8.545594
export PX4_HOME_ALT=488
make px4_sitl gz_x500
9. 결론
본 안내서는 Docker 컨테이너를 활용하여 PX4-Autopilot과 최신 Gazebo 시뮬레이터를 연동하는 전 과정을 종합적으로 다루었다. Docker를 사용함으로써 복잡한 개발 환경 설정의 장벽을 낮추고, 어떤 시스템에서든 일관되고 재현 가능한 시뮬레이션 환경을 구축할 수 있다.
핵심은 호스트와 컨테이너 간의 유기적인 연동에 있다. 소스 코드를 공유하여 개발 편의성을 높이고, X11 포워딩을 통해 GUI 애플리케이션을 원활하게 사용하며, 호스트 네트워킹으로 QGroundControl 및 외부 도구와의 통신을 간소화하는 기법은 필수적이다.
기본적인 단일 기체 시뮬레이션부터 다중 기체 및 ROS 2 연동에 이르기까지, Docker 기반 워크플로우는 개발자가 의존성 문제에서 벗어나 핵심적인 자율 비행 알고리즘 개발에 집중할 수 있도록 돕는다. 본 안내서에서 제시된 기법과 문제 해결 방법론을 통해 개발자와 연구자들이 이 강력하고 유연한 시뮬레이션 플랫폼의 모든 잠재력을 활용하여 더욱 안전하고 지능적인 자율 시스템을 구현해 나가기를 기대한다.
10. 참고 자료
- Your First Jetson Container | NVIDIA Developer, https://developer.nvidia.com/embedded/learn/tutorials/jetson-container
- Docker Setup on JetPack 6 - Jetson Orin - JetsonHacks, https://jetsonhacks.com/2025/02/24/docker-setup-on-jetpack-6-jetson-orin/
- Error with “Nvidia Container Runtime with Docker Integration” on AGX Orin with JP6.2, https://forums.developer.nvidia.com/t/error-with-nvidia-container-runtime-with-docker-integration-on-agx-orin-with-jp6-2/324558
- NVDIA docker container error in jetson agx orin dev kit 64gb – jetpack 6.2, https://forums.developer.nvidia.com/t/nvdia-docker-container-error-in-jetson-agx-orin-dev-kit-64gb-jetpack-6-2/324725
- Docker Setup On Jetson Orin - Includes JetPack 6 Docker fix - YouTube, https://www.youtube.com/watch?v=d2I_wjJTekw
- Installing the NVIDIA Container Toolkit, https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html
- A Beginner’s Guide to NVIDIA Container Toolkit in Machine Vision - UnitX, https://www.unitxlabs.com/resources/nvidia-container-toolkit-machine-vision-system-beginners-guide/
- A Beginner’s Guide to NVIDIA Container Toolkit on Docker | by Umberto Junior Mele, https://medium.com/@u.mele.coding/a-beginners-guide-to-nvidia-container-toolkit-on-docker-92b645f92006
- NVIDIA Container Runtime on Jetson (Beta) — Cloud Native Products documentation, https://nvidia.github.io/container-wiki/toolkit/jetson.html
- NVIDIA L4T is a Linux based software distribution for the NVIDIA Jetson embedded computing platform. - NGC Catalog, https://catalog.ngc.nvidia.com/orgs/nvidia/containers/l4t-base
- ROS 2 on Raspberry Pi — ROS 2 Documentation: Iron documentation, https://docs.ros.org/en/iron/How-To-Guides/Installing-on-Raspberry-Pi.html
- arm64v8/ros - Docker Image, https://hub.docker.com/r/arm64v8/ros/
- osrf/docker_images: A repository to hold definitions of docker images maintained by OSRF - GitHub, https://github.com/osrf/docker_images
- dusty-nv/jetson-containers: Machine Learning Containers for NVIDIA Jetson and JetPack-L4T - GitHub, https://github.com/dusty-nv/jetson-containers
- ROS2 Docker Containers for NVIDIA Jetson, https://nvidia-ai-iot.github.io/ros2_jetson/ros2-jetson-dockers/
- NVIDIA Container Runtime, https://developer.nvidia.com/container-runtime
- What’s difference between –gpus and –runtime=nvidia for the docker container?, https://forums.developer.nvidia.com/t/whats-difference-between-gpus-and-runtime-nvidia-for-the-docker-container/283468
- ROS Development Inside Docker - Medium, https://medium.com/@ruhyadi/ros-development-inside-docker-7c2e411badf6
- nvidia l4t cuda - NGC Catalog, https://catalog.ngc.nvidia.com/orgs/nvidia/containers/l4t-cuda
- Run RViz from remote docker using X11 - ROS Answers archive, https://answers.ros.org/question/349910/
- Run Rviz from remote docker using X11 - opengl - Stack Overflow, https://stackoverflow.com/questions/61283781/run-rviz-from-remote-docker-using-x11
- Accessing USB Devices from Docker Containers - Home Automation Guy, https://www.homeautomationguy.io/blog/docker-tips/accessing-usb-devices-from-docker-containers
- The Complete Beginner’s Guide to Docker for ROS 2 Deployment (2025) - Robotair, https://blog.robotair.io/the-complete-beginners-guide-to-using-docker-for-ros-2-deployment-2025-edition-0f259ca8b378
- Docker - a way to give access to a host USB or serial device? - Stack Overflow, https://stackoverflow.com/questions/24225647/docker-a-way-to-give-access-to-a-host-usb-or-serial-device
- Isaac_ros_nitros colcon build fails in multiple ways on docker run_dev.sh image/container - Isaac ROS - NVIDIA Developer Forums, https://forums.developer.nvidia.com/t/isaac-ros-nitros-colcon-build-fails-in-multiple-ways-on-docker-run-dev-sh-image-container/259340
- Docker container and host communication: source behaves oddly - ROS Answers archive, https://answers.ros.org/question/408379/
- ROS2 connectivity across Docker containers via Host Driver - Robotics Stack Exchange, https://robotics.stackexchange.com/questions/87845/ros2-connectivity-across-docker-containers-via-host-driver
- ROS2 nodes can’t communicate between Docker containers · Issue #5396 · eProsima/Fast-DDS - GitHub, https://github.com/eProsima/Fast-DDS/issues/5396
- ROS2 Docker development practice (volume mounting) - ROS Answers archive, https://answers.ros.org/question/408234/
- Using colcon to build packages - ROS documentation, https://docs.ros.org/en/foxy/Tutorials/Beginner-Client-Libraries/Colcon-Tutorial.html
- Integrating ROS 2 Projects with Docker on ARM64-Based Boards | by Harun KURT, https://medium.com/@harunkurtdev/integrating-ros-2-projects-with-docker-on-arm64-based-boards-c3202363b864
- Devices in Docker - Articulated Robotics, https://articulatedrobotics.xyz/tutorials/docker/devices-docker/